﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using PicoContainer;
using PicoContainer.Defaults;
using XEvolve.Core.Runtime.Attributes;

namespace XEvolve.Core.Runtime.Internal
{
    internal class XComponentAdapter : ConstructorInjectionComponentAdapter
    {
        private readonly IExtensionRegistry registry;

        public XComponentAdapter(IExtensionRegistry registry, object componentKey, Type componentImplementation,
                                 IParameter[] parameters) : base(componentKey, componentImplementation, parameters)
        {
            this.registry = registry;
        }

        public override object GetComponentInstance(IPicoContainer container)
        {
            var greediestSatisfiableConstructor = GetGreediestSatisfiableConstructor(container);
            return greediestSatisfiableConstructor.Invoke(GetConstructorArguments(container, greediestSatisfiableConstructor));
        }

        protected override ConstructorInfo GetGreediestSatisfiableConstructor(IPicoContainer container)
        {
            var constructors =
                ComponentImplementation.GetConstructors().OrderByDescending(ctor => ctor.GetParameters().Length);

            var satisfiableConstructor = constructors.FirstOrDefault(constructor => AreAllParametersDefinedInContainer(constructor.GetParameters(), container));
            if (satisfiableConstructor == null)
            {
                throw new UnsatisfiableDependenciesException("Can't found proper constructor for component " + ComponentImplementation);
            }
            return satisfiableConstructor;
        }

        protected new object[] GetConstructorArguments(IPicoContainer container, ConstructorInfo ctor)
        {
            return ctor.GetParameters().Select(parameter => GetParameterValue(parameter, container)).ToArray();
        }

        private object GetParameterValue(ParameterInfo parameter, IPicoContainer container)
        {
            return HasDependencyAttribute(parameter) ? GetDependencyParameterValue(parameter) : container.GetComponentInstance(parameter.ParameterType);
        }

        private object GetDependencyParameterValue(ParameterInfo parameter)
        {
            var attribute = GetDependencyAttributes(parameter)[0];
            var provideValue = attribute.ProvideValue(registry);
            return parameter.ParameterType.IsArray ? CreateConcerteTypeArray(parameter.ParameterType.GetElementType(), (object[])provideValue) : provideValue;
        }

        private static object CreateConcerteTypeArray(Type elementType, object[] values)
        {
            var parameterValue = Array.CreateInstance(elementType, values.Length);
            values.CopyTo(parameterValue, 0);
            return parameterValue;
        }

        private static bool AreAllParametersDefinedInContainer(IEnumerable<ParameterInfo> parameters, IPicoContainer container)
        {
            return parameters.All(parameter => HasDependencyAttribute(parameter) || container.GetComponentAdapter(parameter.ParameterType) != null);
        }

        private static bool HasDependencyAttribute(ICustomAttributeProvider parameter)
        {
            return GetDependencyAttributes(parameter).Length > 0;
        }

        private static IDependencyAttribute[] GetDependencyAttributes(ICustomAttributeProvider parameter)
        {
            return (IDependencyAttribute[]) parameter.GetCustomAttributes(typeof (IDependencyAttribute), false);
        }
    }
}